home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / chrome / content / foxmarks-places.js < prev    next >
Text File  |  2010-01-28  |  50KB  |  1,380 lines

  1. /*
  2.  Copyright 2008 Foxmarks Inc.
  3.  
  4.  foxmarks-places.js: implements class BookmarkDatasource, encapsulating
  5.  the Firefox Places datasource.
  6.  
  7.  */
  8.  
  9. var Cc = Components.classes;
  10. var Ci = Components.interfaces;
  11. var Cr = Components.results;
  12.  
  13. const MAP_NATIVE_TO_NID = /folder=(\d+)/g
  14. const MAP_NID_TO_NATIVE = /folder=nid-([\w\-]+)/g
  15. const MAP_TAG_TO_NATIVE = /folder=tag-([^&]+)/g
  16. const MAP_NATIVE_TO_TAG = /folder=([^&]+)/g
  17.  
  18. // Comment out this line and uncomment block below to enable
  19. // collection and reporting of timing info.
  20.  
  21. const collectTimingInfo = false;
  22.  
  23. /*
  24.  
  25. const collectTimingInfo = true;
  26.  
  27. function StartTimes(activity) {
  28.     StartTimes.activity = activity;
  29.     StartTimes.start = Date.now();
  30.     ResetTimes();
  31.     Xmarks.LogWrite("Starting " + activity + "...");
  32. }
  33.  
  34. function AddTime(f, t) {
  35.     if (!AddTime.times) {
  36.         AddTime.times = {};
  37.     }
  38.     if (!AddTime.times[f]) {
  39.         AddTime.times[f] = [];
  40.     }
  41.     AddTime.times[f].push(t);
  42. }
  43.  
  44. function ReportTimes() {
  45.     Xmarks.LogWrite("Total time for " + String(StartTimes.activity) + ": " + 
  46.         String(Date.now() -  StartTimes.start));
  47.  
  48.     function min(a) {
  49.         var m;
  50.         forEach(a, function(v) {
  51.             if (!m || v < m) {
  52.                 m = v;
  53.             }
  54.         });
  55.         return m;
  56.     }
  57.  
  58.     function max(a) {
  59.         var m;
  60.         forEach(a, function(v) {
  61.             if (!m || v > m) {
  62.                 m = v;
  63.             }
  64.         });
  65.         return m;
  66.     }
  67.  
  68.     function avg(a) {
  69.         return (tot(a) / a.length).toPrecision(3);
  70.     }
  71.  
  72.     function tot(a) {
  73.         var m = 0;
  74.         forEach(a, function(v) {
  75.             m += v;
  76.         });
  77.         return m;
  78.     }
  79.  
  80.     forEach(AddTime.times, function(v, k) {
  81.             Xmarks.LogWrite("Time: " + k + ": " + String(v.length) + " times, " +
  82.                 String(tot(v)) + "ms total (" + 
  83.                 String(min(v)) + "/" + String(max(v)) + "/" + String(avg(v)) +
  84.                 ")");
  85.     });
  86. }
  87.  
  88. function ResetTimes() {
  89.     AddTime.times = {};
  90. }
  91.  
  92. */
  93.  
  94. function Call(o, f) {
  95.     const functionRE = /function (\w+)\(\)/
  96.     var args = [];
  97.     if (arguments.length > 2) {
  98.         args = Array.prototype.slice.call(arguments, 2);
  99.     }
  100.  
  101.     try {
  102.         if (collectTimingInfo) {
  103.             var start = Date.now();
  104.         }
  105.         var returnVal =  f.apply(o, args);
  106.         if (collectTimingInfo) {
  107.             var end = Date.now();
  108.             AddTime(f.toSource().match(functionRE)[1], end - start);
  109.         }
  110.         return returnVal;
  111.     } catch (e) {
  112.         // Detect specific errors and map them to our own
  113.         switch (e.result) {
  114.         case Cr.NS_ERROR_FILE_CORRUPTED:
  115.             throw 5;
  116.         default:
  117.             throw Error("Places error calling " + f + " with args " + 
  118.                 args.toSource() + " Original error: " + e);
  119.         }
  120.     }
  121. }
  122.  
  123. function ParseIconString(s) {
  124.     var exp = /^data:(.*);base64,(.*)$/;
  125.     var match = exp.exec(s);
  126.  
  127.     if (match) {
  128.         try {
  129.             var data = My_atob(match[2]);
  130.         } catch (e) {
  131.             return null;
  132.         }
  133.         return [match[1] /* mime type */, data /* b64-encoded data */];
  134.     } else {
  135.         return null;
  136.     }
  137. }
  138.  
  139. // Node's representation of dates is seconds since 1/1/1970.
  140. // Mozilla's representation of dates is microseconds since 1/1/1970.
  141. // Use these utility functions to convert between the two formats
  142. // while minimizing rounding error.
  143.  
  144. function DatePlacesToNode(v) {
  145.     return Math.round(v / 1000000);
  146. }
  147.  
  148. function DateNodeToPlaces(v) {
  149.     return (v || 0) * 1000000;
  150. }
  151.  
  152.    
  153.  
  154.  
  155. function BookmarkDatasource() {
  156.     this.InitServices();
  157.  
  158.     if (!BookmarkDatasource._mapNidToNative) {
  159.         this._InitNidNativeMaps();
  160.     }
  161.  
  162.     if (!BookmarkDatasource._locationMap) {
  163.         this._InitLocationMap();
  164.     }
  165.  
  166.     // defined in foxmarks-sync.js
  167.     applyCommonBookmarkFunctions(this);
  168. }
  169.  
  170. BookmarkDatasource.SERVICES = [
  171.     ["bmsvc", "@mozilla.org/browser/nav-bookmarks-service;1", 
  172.         Ci.nsINavBookmarksService],
  173.     ["hsvc", "@mozilla.org/browser/nav-history-service;1", 
  174.         Ci.nsINavHistoryService],
  175.     ["asvc", "@mozilla.org/browser/annotation-service;1", 
  176.         Ci.nsIAnnotationService],
  177.     ["lmsvc", "@mozilla.org/browser/livemark-service;2",
  178.         Ci.nsILivemarkService],
  179.     ["tsvc", "@mozilla.org/browser/tagging-service;1",
  180.         Ci.nsITaggingService],
  181.     ["fisvc", "@mozilla.org/browser/favicon-service;1",
  182.         Ci.nsIFaviconService],
  183.     ["mssvc", "@mozilla.org/microsummary/service;1",
  184.         Ci.nsIMicrosummaryService],
  185.     ["fusvc", "@mozilla.org/docshell/urifixup;1",
  186.         Ci.nsIURIFixup]
  187. ];
  188.  
  189.  
  190. BookmarkDatasource.STORAGE_ENGINE = "/Places";
  191. BookmarkDatasource.startupTime = Date.now();
  192. BookmarkDatasource.sessionId = BookmarkDatasource.startupTime.toString(36);
  193. BookmarkDatasource.nidIndex = 0;
  194. BookmarkDatasource.MAX_NID_LENGTH = 32;
  195. BookmarkDatasource.LOCATION_MAP_ANNO_NAME = "foxmarks/locationMap";
  196. BookmarkDatasource.MAP_PLACE_TYPE_TO_NTYPE = 
  197.     { 0 : "bookmark", 5: "query", 6: "folder", 7: "separator", 9: "query" };
  198. BookmarkDatasource.MAP_NTYPE_TO_PLACE_TYPE = { 
  199.     "bookmark": [0, 5, 9], 
  200.     "microsummary": [0], 
  201.     "query": [0, 5, 9], 
  202.     "folder": [6], 
  203.     "feed": [6], 
  204.     "separator": [7]};
  205.  
  206. BookmarkDatasource.MAP_ANNO_TO_NODE = {
  207.     "bookmarkProperties/description"    : ["description"],
  208.     "bookmarkProperties/POSTData"       : ["formdata"],
  209.     "bookmarkProperties/loadInSidebar"  : ["sidebar", 
  210.                                             function(x) { return true; }],
  211.     "livemark/feedURI"                  : ["feedurl"],
  212.     "livemark/siteURI"                  : ["url"],
  213.     "microsummary/generatorURI"         : ["generateduri"],
  214.     "bookmarks/contentType"             : ["contenttype"]
  215. };
  216.  
  217. BookmarkDatasource.MAP_NODE_TO_ANNO = {
  218.     "description"   : "bookmarkProperties/description",
  219.     "formdata"      : "bookmarkProperties/POSTData",
  220.     "sidebar"       : "bookmarkProperties/loadInSidebar",
  221.     "feedurl"       : "livemark/feedURI",
  222.     "generateduri"  : "microsummary/generatorURI",
  223.     "contentType"   : "bookmarks/contentType"
  224. };
  225.  
  226. BookmarkDatasource.INTERESTING_PROPERTIES = {
  227.     "title"         : true,
  228.     "keyword"       : true,
  229.     "uri"           : true
  230. };
  231.  
  232. BookmarkDatasource.KNOWN_ATTRS = {
  233.     "ntype"         : true,
  234.     "description"   : true,
  235.     "formdata"      : true,
  236.     "sidebar"       : true,
  237.     "feedurl"       : true,
  238.     "generateduri"  : true,
  239.     "contentType"   : true,
  240.     "name"          : true,
  241.     "created"       : true,
  242.     "modified"      : true,
  243.     "visited"       : true,
  244.     "shortcuturl"   : true,
  245.     "nid"           : true,
  246.     "pnid"          : true,
  247.     "children"      : true,
  248.     "url"           : true,
  249.     "icon"          : true,
  250.     "tags"          : true,
  251.     "tnid"          : true,
  252.     "unid"          : true
  253. };
  254.  
  255. BookmarkDatasource.MapPlacesToNode = function(place, pnid, children) {
  256.     var self = this;
  257.  
  258.     if (!(place instanceof Ci.nsINavHistoryResultNode)) {
  259.         throw Error("Unknown object type " + place);
  260.     }
  261.  
  262.     if (!(place.type in BookmarkDatasource.MAP_PLACE_TYPE_TO_NTYPE)) {
  263.         Xmarks.LogWrite("Warning: Unhandled result type " + place.type);
  264.         return 0;
  265.     }
  266.  
  267.     var node = new Node(this.MapNative(place.itemId));
  268.     node.pnid = pnid;
  269.     node.ntype = BookmarkDatasource.MAP_PLACE_TYPE_TO_NTYPE[place.type];
  270.     if (node.ntype == 'folder' && Call(this.lmsvc, this.lmsvc.isLivemark, 
  271.             place.itemId)) {
  272.         node.ntype = 'feed';
  273.     } else if (node.ntype == 'bookmark' && 
  274.             Call(this.mssvc, this.mssvc.hasMicrosummary, place.itemId)) {
  275.         node.ntype = 'microsummary';
  276.     }
  277.  
  278.     if (place.title && place.title.length) {
  279.         node.name = place.title;
  280.     }
  281.     if (node.ntype == 'bookmark' || node.ntype == 'query' || 
  282.             node.ntype == 'microsummary') {
  283.         node.url = place.uri;
  284.         if (node.ntype == 'query') {
  285.             try {
  286.                 // Test to see if it's a tag query.
  287.                 if (place.QueryInterface(Ci.nsINavHistoryQueryResultNode).
  288.                         queryOptions.resultType == 
  289.                         Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
  290.                     node.url = node.url.replace(
  291.                             MAP_NATIVE_TO_TAG, function(x,y) {
  292.                             return "folder=tag-" + 
  293.                                 encodeURIComponent(node.name);
  294.                     });
  295.                 } else {
  296.                     node.url = node.url.replace(
  297.                             MAP_NATIVE_TO_NID, function(x, y) {
  298.                         return "folder=nid-" + self.MapNative(y, true);
  299.                     });
  300.                 }
  301.             } catch (e) {
  302.                 Xmarks.LogWrite("Warning: Failed mapping " + node.toSource() +
  303.                     ". Error is " + e.toSource());
  304.             }
  305.         } 
  306.     }
  307.     if (place.dateAdded) 
  308.         node.created = DatePlacesToNode(place.dateAdded);
  309.     if (place.lastModified)
  310.         node.modified = DatePlacesToNode(place.lastModified);
  311.     if (place.time)
  312.         node.visited = DatePlacesToNode(place.time);
  313.     var keyword = Call(self.bmsvc, self.bmsvc.getKeywordForBookmark, 
  314.         place.itemId);
  315.     if (keyword) {
  316.         node.shortcuturl = keyword;
  317.     }
  318.     if (node.ntype == 'folder' && children) {
  319.         node.children = children;
  320.     }
  321.  
  322.     if (node.ntype == 'bookmark' && node.url) {
  323.         var uri = self.NewURI(node.url);
  324.         var iconUri = null;
  325.         try {
  326.             iconUri = Call(self.fisvc, self.fisvc.getFaviconForPage, uri);
  327.         } catch(e) {}
  328.         if (iconUri) {
  329.             var mimeType = {};
  330.             var iconData = null;
  331.             try {
  332.                 iconData = Call(self.fisvc, self.fisvc.getFaviconData, iconUri, 
  333.                     mimeType, {});
  334.             } catch(e) {}
  335.             if (iconData && iconData.length > 0) {
  336.                 node.icon = "data:" + mimeType.value + ";base64," + 
  337.                     Base64.encode(iconData, true);
  338.             }
  339.         }
  340.     }
  341.  
  342.     forEach(BookmarkDatasource.MAP_ANNO_TO_NODE, function(attr, k) {
  343.         var value = GetAnnotation(place.itemId, k);
  344.         if (value) {
  345.             node[attr[0]] = attr[1] ? attr[1](value) : value;
  346.         }
  347.     } );
  348.  
  349.     if (place.uri) {
  350.         var uri = self.NewURI(place.uri);
  351.         var tags = uri ? Call(this.tsvc, this.tsvc.getTagsForURI, uri, {}) : [];
  352.         if (tags.length) {
  353.             node.tags = tags.slice().filter(function(x) { 
  354.                     return x && x.length; });
  355.             // Clone it so we get toJSONString, etc.
  356.         }
  357.     }
  358.  
  359.     this.pn.AddNode.apply(this.pn.Caller, [node]);
  360.     return 0;
  361.  
  362.     function GetAnnotation(itemId, anno) {
  363.         try {
  364.             return Call(self.asvc, self.asvc.getItemAnnotation, itemId, anno);
  365.         } catch (e) {
  366.             return null;
  367.         }
  368.     }
  369. }
  370.  
  371. BookmarkDatasource.ProvideNodesDone = function(status) {
  372.     if (!status) {
  373.         // Order is significant here; apply in reverse order
  374.         // from that in which these were accepted.
  375.         this._ApplyLocationMap(this.pn.Caller, 
  376.             this.bmsvc.unfiledBookmarksFolder);
  377.         this._ApplyLocationMap(this.pn.Caller,
  378.             this.bmsvc.toolbarFolder);
  379.         this.pn.Caller.Node(NODE_ROOT, true).tnid =
  380.             this.MapNative(this.bmsvc.toolbarFolder);
  381.         this.pn.Caller.Node(NODE_ROOT, true).unid =
  382.             this.MapNative(this.bmsvc.unfiledBookmarksFolder);
  383.     }
  384.     this.pn.Caller.placesSource = true;
  385.     this.pn.Complete.apply(this.pn.Caller, [status]);
  386.     this.pn = null;
  387.     if (collectTimingInfo)
  388.         ReportTimes();
  389.     return;
  390. }
  391.  
  392. // ot is the static object that maintains state for OnTree,
  393. // the tree-walker.
  394.  
  395. var ot = {}
  396.  
  397. BookmarkDatasource.prototype = {
  398.     InitServices: function() {
  399.         this.initialized = true;
  400.         for (var i = 0; i < BookmarkDatasource.SERVICES.length; ++i) {
  401.             var service = BookmarkDatasource.SERVICES[i];
  402.             try {
  403.                 this[service[0]] = Cc[service[1]].getService(service[2]);
  404.             } catch (e) {
  405.                 Xmarks.LogWrite("Failed to initialize " + service.toSource() + 
  406.                         ": error is " + e.toSource());
  407.                 this.initialized = false;
  408.             }
  409.         }
  410.     },
  411.  
  412.     //
  413.     // Nid <-> Native maps
  414.     //
  415.  
  416.     // We use Places' GUID as our nids. These functions
  417.     // provide mapping services between GUID/nid and the
  418.     // native itemid.
  419.  
  420.     _InitNidNativeMaps: function() {
  421.         BookmarkDatasource._mapNidToNative = {};
  422.         BookmarkDatasource._mapNativeToNid = {};
  423.         this.AddToMap(this.bmsvc.bookmarksMenuFolder, NODE_ROOT);
  424.     },
  425.  
  426.     MapNid: function(nid) {
  427.         return BookmarkDatasource._mapNidToNative[nid] || 
  428.             Call(this.bmsvc, this.bmsvc.getItemIdForGUID, nid);
  429.     },
  430.  
  431.     MapNative: function(itemId, silent) {
  432.  
  433.         // Try the map first.
  434.         var nid = BookmarkDatasource._mapNativeToNid[itemId];
  435.         if (nid)
  436.             return nid;
  437.  
  438.         // Try to get a reasonable length GUID.
  439.         try {
  440.             nid = Call(this.bmsvc, this.bmsvc.getItemGUID, itemId);
  441.         } catch(e) {
  442.             if (!silent) {
  443.                 Xmarks.LogWrite("MapNative failed with itemId = " + itemId);
  444.                 Xmarks.LogWrite("Original error is " + e.toSource());
  445.                 Xmarks.LogWrite("Caller is " + this.MapNative.caller);
  446.             }
  447.             throw e;
  448.         }
  449.         if (nid.length < BookmarkDatasource.MAX_NID_LENGTH)
  450.             return nid;
  451.  
  452.         // No? Make our own.
  453.         nid = this.GenerateNid();
  454.         Call(this.bmsvc, this.bmsvc.setItemGUID, itemId, nid);
  455.         return nid;
  456.     },
  457.  
  458.     AddToMap: function(itemId, nid) {
  459.         BookmarkDatasource._mapNidToNative[nid] = itemId;
  460.         BookmarkDatasource._mapNativeToNid[itemId] = nid;
  461.         return nid;
  462.     },
  463.  
  464.     GenerateNid: function() {
  465.         return BookmarkDatasource.sessionId + "-" +
  466.             (BookmarkDatasource.nidIndex++).toString(36);
  467.     },
  468.  
  469.  
  470.     //
  471.     // LocationMaps
  472.     //
  473.  
  474.     /* For the special folders (toolbar and unfiled), we maintain
  475.        an annotation to persist those folders' location in the nodeset.
  476.        The location is represented as a [pnid, bnid] combination.
  477.        The annotation itself is just the toSource() representation of the
  478.        LocationMap dict, which is keyed off the itemId.
  479.      */
  480.  
  481.     _InitLocationMap: function() {
  482.         try {
  483.             BookmarkDatasource._locationMap = 
  484.                 eval(Call(this.asvc, this.asvc.getItemAnnotation, 
  485.                         this.bmsvc.bookmarksMenuFolder,
  486.                         BookmarkDatasource.LOCATION_MAP_ANNO_NAME));
  487.         } catch(e) {
  488.             BookmarkDatasource._locationMap = {};
  489.             BookmarkDatasource._locationMap[this.bmsvc.toolbarFolder] = 
  490.                 [null, null];
  491.             BookmarkDatasource._locationMap[this.bmsvc.unfiledBookmarksFolder] = 
  492.                 [null, null];
  493.         }
  494.     },
  495.  
  496.     _WriteLocationMap: function() {
  497.         var lm = BookmarkDatasource._locationMap.toSource();
  498.         Call(this.asvc, this.asvc.setItemAnnotation, 
  499.             this.bmsvc.bookmarksMenuFolder,
  500.             BookmarkDatasource.LOCATION_MAP_ANNO_NAME, lm, 0, 
  501.             this.asvc.EXPIRES_NEVER);
  502.     },
  503.  
  504.     _ModifyLocationMap: function(itemId, pnid, bnid) {
  505.         var val = [pnid, bnid];
  506.         if (!equals(BookmarkDatasource._locationMap[itemId], val)) {
  507.             Xmarks.LogWrite("Modifying locationmap for " + itemId + " from " +
  508.                     BookmarkDatasource._locationMap[itemId].toSource() + " to " + 
  509.                     val.toSource());
  510.             BookmarkDatasource._locationMap[itemId] = val;
  511.             this._WriteLocationMap();
  512.         }
  513.     },
  514.  
  515.     _ApplyLocationMap: function(ns, itemId) {
  516.         // Move special node into the correct position
  517.         var v = BookmarkDatasource._locationMap[itemId];
  518.         if (!v)
  519.             return;
  520.  
  521.         var nid = this.MapNative(itemId);
  522.         var pnid = v[0];
  523.         var bnid = v[1];
  524.  
  525.         if (!ns.Node(nid, false, true)) {
  526.             return;
  527.         }
  528.  
  529.         if (!pnid || !ns.Node(pnid, false, true)) {
  530.             pnid = NODE_ROOT;
  531.             bnid = null;
  532.         }
  533.  
  534.         if (bnid && (!ns.Node(pnid)["children"] ||
  535.                 ns.Node(pnid).children.indexOf(bnid) < 0)) {
  536.             bnid = null;
  537.         }
  538.  
  539.         ns.InsertInParent(nid, pnid, bnid);
  540.     },
  541.  
  542.     //
  543.     //
  544.     //
  545.  
  546.     BaselineLoaded: function(baseline, callback) {
  547.         // NYI
  548.         callback(0);
  549.         return;
  550.     },
  551.  
  552.     NewURI: function(str) {
  553.         var uri = null;
  554.         try {
  555.             uri = Cc["@mozilla.org/network/io-service;1"].
  556.                 getService(Ci.nsIIOService).
  557.                 newURI(str, "UTF-8", null);
  558.         } catch(e) {}
  559.         if (!uri) {
  560.             Xmarks.LogWrite("Failed to produce uri for " + str);
  561.         }
  562.         return uri;
  563.     },
  564.  
  565.     FixedURI: function(str) {
  566.         var fixed = null;
  567.         try {
  568.             fixed = Call(this.fusvc, this.fusvc.createFixupURI, str, 0);
  569.         } catch (e) {
  570.             fixed = Call(this.fusvc, this.fusvc.createFixupURI, 
  571.                 "about:blank", 0)
  572.         }
  573.         return fixed;
  574.     },
  575.  
  576.     NormalizeUrl: function(str) {
  577.         return this.FixedURI(str).spec;
  578.     },
  579.  
  580.     runBatched: function() {
  581.         this._func.apply(this, this._args);
  582.     },
  583.  
  584.  
  585.     //
  586.     // Functions for writing to the native store.
  587.     //
  588.  
  589.     AcceptNodes: function(ns, callback) {
  590.         if (collectTimingInfo)
  591.             StartTimes("AcceptNodes");
  592.         this._func = this._AcceptNodes;
  593.         this._args = arguments;
  594.         this.bmsvc.runInBatchMode(this, null);
  595.         if (collectTimingInfo)
  596.             ReportTimes();
  597.     },
  598.  
  599.     _AcceptNodes: function(ns, callback) {
  600.         /*
  601.  
  602.          Here's the strategy. Execute a query on the top-level of the
  603.          bookmarks hierarchy. Push the result for that root onto the stack,
  604.          along with the matching root of the nodeset.
  605.  
  606.          Pop the next pair off the stack. Compare direct properties, and make
  607.          adjustments as necessary. If the nodes being compared are folders
  608.          deal with their children as follows:
  609.  
  610.          (1) First, make sure that all of the children in the node exist
  611.              on the Places side. This means creating entities that don't exist
  612.              at all, and moving entities that exist in a different parent.
  613.          (2) Delete any item that exists on the Places side but not at all
  614.              on in the nodeset.
  615.          (3) Iterate over the node's children, setting position indices in
  616.              the Places children as appropriate.
  617.          (4) Iterate over the node children again, pushing the appropriate
  618.              pairs onto the stack.
  619.  
  620.          Dealing with toolbar: we're going to examine the tnid for the
  621.          incoming nodeset and see if it's the same as the nid for the
  622.          Places toolbarFolder. If it is different, we're
  623.          going to change the nid on the toolbarFolder to match the new tnid.
  624.  
  625.          We'll also be looking at the location of the toolbar, and updating
  626.          the location map if necessary to correspond to the toolbar folder's
  627.          current location.
  628.  
  629.          Finally, we process the toolbar folder separately, dealing with it
  630.          only as a root and not as a child (that is, if we see it come
  631.          through as a child in the nodeset, we will ignore it).
  632.  
  633.          */
  634.  
  635.         var items = [];
  636.         var fixupFolderQueries = [];
  637.         var fixupTagQueries = []
  638.         var self = this;
  639.         var status = 0;
  640.  
  641.         var optimizeOK =  ns._cloneSource && ns._cloneSource.placesSource;
  642.  
  643.         try {
  644.             if (!this.initialized) {
  645.                 throw 6;
  646.             }
  647.             var tnid = ns.Node(NODE_ROOT).tnid;
  648.             var unid = ns.Node(NODE_ROOT).unid;
  649.  
  650.             var specialNids = [];
  651.             AcceptNewRoot(this.bmsvc.toolbarFolder, tnid);
  652.             AcceptNewRoot(this.bmsvc.unfiledBookmarksFolder, unid);
  653.  
  654.             this.PushRoot(this.bmsvc.bookmarksMenuFolder, items);
  655.             this.PushRoot(this.bmsvc.toolbarFolder, items);
  656.             this.PushRoot(this.bmsvc.unfiledBookmarksFolder, items);
  657.  
  658.             while (items.length) {
  659.                 var item = items.shift();
  660.                 var place = item[0];
  661.                 var itemId = place.itemId;
  662.                 var nid = item[1];
  663.                 var node = ns.Node(nid, false, true);
  664.                 if (!node) {
  665.                     // Node in question was deleted; sync children
  666.                     // and be done.
  667.                     SynchronizeChildren(true);
  668.                     continue;
  669.                 }
  670.  
  671.                 if (node.ntype == 'query') {
  672.                     if (MAP_NID_TO_NATIVE.test(node.url)) {
  673.                         Xmarks.LogWrite("Found folder query to fix up: " + 
  674.                                 node.url);
  675.                         fixupFolderQueries.push(itemId);
  676.                     }
  677.                     if (MAP_TAG_TO_NATIVE.test(node.url)) {
  678.                         Xmarks.LogWrite("Found tag query to fix up: " +
  679.                                 node.url);
  680.                         fixupTagQueries.push(itemId);
  681.                     }
  682.                 }
  683.  
  684.                 var uri = self.FixedURI(node.url);
  685.  
  686.                 if (!optimizeOK || ns._node[nid]) {
  687.                     SynchronizeDirectProperties();
  688.                     SynchronizeIcons();
  689.                     SynchronizeAnnotations();
  690.                     SynchronizeTags();
  691.                     SynchronizeChildren(false);
  692.                 }
  693.  
  694.                 if (node.ntype == 'folder' &&
  695.                         place instanceof Ci.nsINavHistoryContainerResultNode) {
  696.                     place.containerOpen = true;
  697.                     for (var i = 0; i < place.childCount; ++i) {
  698.                         var child = place.getChild(i);
  699.                         items.push([child, self.MapNative(child.itemId), nid]);
  700.                     }
  701.                     place.containerOpen = false;
  702.                 }
  703.             }
  704.  
  705.             forEach(fixupFolderQueries, function(itemId) {
  706.                 try {
  707.                     var uri = Call(self.bmsvc, self.bmsvc.getBookmarkURI, 
  708.                         itemId).spec;
  709.                     uri = uri.replace(MAP_NID_TO_NATIVE, function(x, y) {
  710.                         return "folder=" + self.MapNid(y);
  711.                     });
  712.                     Call(self.bmsvc, self.bmsvc.changeBookmarkURI, itemId, 
  713.                         self.NewURI(uri));
  714.                 } catch(e) {
  715.                     Xmarks.LogWrite(
  716.                         "Warning: Couldn't map folder-nid to itemId " +
  717.                         "for " + itemId + ". Error is " + e.toSource());
  718.                 }
  719.             });
  720.  
  721.             if (fixupTagQueries.length) {
  722.                 // Build a hash that maps each tag to the itemId of its
  723.                 // tag folder. This is clearly expensive, but I'm not aware
  724.                 // of any other way to get at this information.
  725.                 var q = Call(self.hsvc, self.hsvc.getNewQuery);
  726.                 var o = Call(self.hsvc, self.hsvc.getNewQueryOptions);
  727.                 q.setFolders([self.bmsvc.tagsFolder], 1);
  728.                 var results = Call(self.hsvc, self.hsvc.executeQuery, q, o);
  729.                 var r = results.root;
  730.                 r.containerOpen = true;
  731.                 var tagMap = {}
  732.                 var childCount = r.childCount;
  733.                 for (var i = 0; i < childCount; ++i) {
  734.                     var c = r.getChild(i);
  735.                     tagMap[c.title] = c.itemId;
  736.                 }
  737.                 r.containerOpen = false;
  738.             }
  739.  
  740.  
  741.             forEach(fixupTagQueries, function(itemId) {
  742.                 try {
  743.                     var uri = Call(self.bmsvc, self.bmsvc.getBookmarkURI,
  744.                         itemId).spec;
  745.                     var newUri = uri.replace(MAP_TAG_TO_NATIVE, function(x, y) {
  746.                         var tagFolderId = tagMap[decodeURIComponent(y)];
  747.                         if (!tagFolderId) {
  748.                             Xmarks.LogWrite("Warning: couldn't map " + y);
  749.                         }
  750.                         return "folder=" + String(tagFolderId);
  751.                     });
  752.                     Xmarks.LogWrite("Mapped incoming uri from " + uri + " to " +
  753.                             newUri);
  754.                     Call(self.bmsvc, self.bmsvc.changeBookmarkURI, itemId,
  755.                         self.NewURI(newUri));
  756.                 } catch (e) {
  757.                     Xmarks.LogWrite("Warning: Couldn't map tagname for item " +
  758.                         itemId + ". Error is " + e.toSource());
  759.                 }
  760.             });
  761.                 
  762.         } catch(e) {
  763.             Xmarks.LogWrite("Exception in AcceptNodes: " + e);
  764.             status = typeof(e) == "number" ? e : 3;
  765.         }
  766.         callback(status);
  767.         return;
  768.  
  769.         function AcceptNewRoot(itemId, nid) {
  770.             var nidValid = nid && ns.Node(nid, false, true) != null;
  771.             if (!nidValid || self.MapNative(itemId) != nid) {
  772.                 optimizeOK = false;     // Can't optimize write if root changes.
  773.                 var oldId = Call(self.bmsvc, self.bmsvc.getItemIdForGUID, nid);
  774.                 if (oldId >= 0) {
  775.                     // If the folder that is the new root exists as an
  776.                     // ordinary folder in places, make the old folder itself go
  777.                     // away by giving it a new nid. (It will get deleted after 
  778.                     // its children get moved into the new place.)
  779.                     Call(self.bmsvc, self.bmsvc.setItemGUID, oldId, 
  780.                         self.GenerateNid());
  781.                 }
  782.                 Call(self.bmsvc, self.bmsvc.setItemGUID, itemId, 
  783.                     nidValid ? nid : self.GenerateNid());
  784.             }
  785.  
  786.             if (nidValid) {
  787.                 self._ModifyLocationMap(itemId, ns.Node(nid).pnid,
  788.                     NextSibling(nid));
  789.             } else {
  790.                 self._ModifyLocationMap(itemId, null, null);
  791.             }
  792.             specialNids.push(nid);
  793.         }
  794.  
  795.         function NextSibling(nid) {
  796.             var siblings = ns.Node(ns.Node(nid).pnid).children;
  797.             var index = siblings.indexOf(nid) + 1;
  798.             while (specialNids.indexOf(siblings[index]) >= 0 &&
  799.                     index < siblings.length) {
  800.                 index++;
  801.             }
  802.             return siblings[index];
  803.         }
  804.  
  805.         function SynchronizeDirectProperties() {
  806.             if (BookmarkDatasource.MAP_NTYPE_TO_PLACE_TYPE[node.ntype].
  807.                     indexOf(place.type) < 0) {
  808.                 // Whoops! The type within places doesn't match our
  809.                 // ntype. Rather than throwing an error, resolve the
  810.                 // situation by removing the offending places item
  811.                 // and creating a new one that matches the specs of
  812.                 // the ntype as best it can.
  813.                 Xmarks.LogWrite("Dealing with type mismatch: ntype is " +
  814.                         node.ntype + " place.type = " + place.type);
  815.                 var parentId = self.MapNid(node.pnid);
  816.                 Call(self.bmsvc, self.bmsvc.removeItem, itemId);
  817.                 switch (node.ntype) {
  818.                 case "bookmark":
  819.                 case "query":
  820.                     itemId = Call(self.bmsvc, self.bmsvc.insertBookmark, 
  821.                         parentId, uri, -1, node.name || "");
  822.                     break;
  823.                 case "folder":
  824.                     itemId = Call(self.bmsvc, self.bmsvc.createFolder,
  825.                         parentId, node.name || "", -1);
  826.                     break;
  827.                 default:
  828.                     throw Error("Type mismatch for " + ns.NodeName(nid) +
  829.                         "place.type = " + place.type + " node.ntype = " +
  830.                         node.ntype);
  831.                 }
  832.                 // Now, get a place object for the item we just created.
  833.                 // The available API's make this pretty difficult.
  834.                 var o = Call(self.hsvc, self.hsvc.getNewQueryOptions);
  835.                 var q = Call(self.hsvc, self.hsvc.getNewQuery);
  836.                 q.setFolders([parentId], 1)
  837.                 var r = Call(self.hsvc, self.hsvc.executeQuery, q, o);
  838.                 var rootNode = r.root;
  839.                 rootNode.containerOpen = true;
  840.                 for (var i = 0; i < rootNode.childCount; ++i) {
  841.                     place = rootNode.getChild(i);
  842.                     if (place.itemId == itemId)
  843.                         break;
  844.                 }
  845.                 rootNode.containerOpen = false;
  846.                 if (i >= rootNode.childCount) {
  847.                     throw Error("Didn't find item just created, nid=" +
  848.                            node.nid);
  849.                 }
  850.                 
  851.                 // Update the map.
  852.                 self.AddToMap(itemId, node.nid);
  853.                 
  854.                 // And fall through...
  855.             }
  856.  
  857.             if (node.ntype == 'bookmark' || node.ntype == 'query' ||
  858.                     node.ntype == 'microsummary') {
  859.                 if (place.uri != node.url) {
  860.                     Call(self.bmsvc, self.bmsvc.changeBookmarkURI, itemId, uri);
  861.                     // Conversion to URI may alter exact text, so
  862.                     // modify it in incoming node if it changed.
  863.                     if (uri.spec != node.url) {
  864.                         ns.Node(nid, true).url = uri.spec;
  865.                     }
  866.                 }
  867.             } else if (node.ntype == 'feed') {
  868.                 Call(self.asvc, self.asvc.setItemAnnotation, itemId, 
  869.                     "livemark/siteURI", node.url || "", 0, 
  870.                     self.asvc.EXPIRE_NEVER);
  871.             } else if (node.ntype == 'separator') {
  872.                 if (node.name) {
  873.                     delete node["name"];
  874.                 }
  875.             }
  876.  
  877.             if (place.title != node.name) {
  878.                 Call(self.bmsvc, self.bmsvc.setItemTitle, itemId, 
  879.                     node.name || "");
  880.             }
  881.  
  882.             if (DatePlacesToNode(place.dateAdded) != (node.created || 0)) {
  883.                 Call(self.bmsvc, self.bmsvc.setItemDateAdded, itemId, 
  884.                     DateNodeToPlaces(node.created));
  885.             }
  886.  
  887.             if (DatePlacesToNode(place.lastModified) != (node.modified || 0)) {
  888.                 Call(self.bmsvc, self.bmsvc.setItemLastModified, itemId, 
  889.                     DateNodeToPlaces(node.modified));
  890.             }
  891.  
  892.             if (Call(self.bmsvc, self.bmsvc.getKeywordForBookmark, itemId) !=
  893.                     node.shortcuturl) {
  894.                 Call(self.bmsvc, self.bmsvc.setKeywordForBookmark, itemId, 
  895.                     node.shortcuturl);
  896.             }
  897.  
  898.             forEach(node, function(v, k) {
  899.                 if (!BookmarkDatasource.KNOWN_ATTRS[k] && 
  900.                         typeof v != "function") {
  901.                     Xmarks.LogWrite("Warning: deleting unknown attr " + k);
  902.                     delete ns.Node(nid, true)[k];
  903.                 }
  904.             });
  905.         }
  906.  
  907.         function SynchronizeIcons() {
  908.             if (node.url && uri && node.ntype == 'bookmark' || 
  909.                     node.ntype == 'microsummary') {
  910.                 var parsedIcon = node.icon ? 
  911.                     ParseIconString(node.icon) : null;
  912.                 if (parsedIcon) {
  913.                     // To store favicon data:
  914.                     // (1) Compare it against what we've already got.
  915.                     //     If it's unchanged, skip it.
  916.                     // (2) Generate a new uri.
  917.                     // (3) Store the data for that uri.
  918.                     // (4) Set the favicon uri for the node url.
  919.  
  920.                     var iconUri = null;
  921.                     try {
  922.                         iconUri = Call(self.fisvc, self.fisvc.getFaviconForPage,
  923.                             uri);
  924.                     } catch(e) {}
  925.                     if (iconUri) {
  926.                         var mimeType = {};
  927.                         var iconData = Call(self.fisvc, 
  928.                             self.fisvc.getFaviconData, iconUri, mimeType, {});
  929.                     }
  930.  
  931.                     if (!iconUri || mimeType != parsedIcon[0] || iconData != 
  932.                             parsedIcon[1]) {
  933.                         var newIconUri = 
  934.                             self.NewURI("http://icon.xmarks.com/" +
  935.                                 self.GenerateNid());
  936.                         // don't fail if firefox can't set the favicon data
  937.                         try {
  938.                             Call(self.fisvc, self.fisvc.setFaviconData, newIconUri, 
  939.                                 parsedIcon[1], parsedIcon[1].length, parsedIcon[0],
  940.                                 Number.MAX_VALUE);
  941.                             Call(self.fisvc, self.fisvc.setFaviconUrlForPage, uri, 
  942.                                 newIconUri);
  943.                         } catch(e){
  944.                             // intentionally blank
  945.                         }
  946.                     }
  947.                 } else {
  948.                     // Clear icon if it exists.
  949.                     var iconUri = null;
  950.                     try {
  951.                         iconUri = Call(self.fisvc, 
  952.                             self.fisvc.getFaviconForPage, uri);
  953.                     } catch (e) {}
  954.                     if (iconUri) {
  955.                         // don't fail if firefox can't set the favicon data
  956.                         try {
  957.                             Call(self.fisvc, self.fisvc.setFaviconData, iconUri, 
  958.                                 null, 0, null, 0);
  959.                         } catch(e){
  960.                             // intentionally blank
  961.                         }
  962.                     }
  963.                     // Clear incoming icon if it existed and was invalid.
  964.                     if (node.icon) {
  965.                         ns.Node(nid, true).icon = null;
  966.                     }
  967.                 }
  968.             }
  969.         }
  970.  
  971.         function SynchronizeAnnotations() {
  972.             // Annotation stragegy:
  973.             // (1) Get a list of all annotations for this place.
  974.             // (2) Filter that list down to the annos we're interested in.
  975.             // (3) Build a list of annotation-stored attributes set in the node.
  976.             // (4) Sync the two lists: process changes, adds, deletes.
  977.  
  978.             var placeAnnos = Call(self.asvc, self.asvc.getItemAnnotationNames, 
  979.                 itemId, {});
  980.             placeAnnos = placeAnnos.filter(function(x) { 
  981.                 return (x in BookmarkDatasource.MAP_ANNO_TO_NODE);
  982.             });
  983.  
  984.             var nodeAnnos = [];
  985.             forEach(BookmarkDatasource.MAP_NODE_TO_ANNO, function(v, x) { 
  986.                 if (node[x]) {
  987.                     nodeAnnos.push(x);
  988.                 }
  989.             });
  990.  
  991.             // Exceptional case: if it's a feed, the url maps to
  992.             // the livemark/siteURI annotation.
  993.             if (node.ntype == 'feed' && node.url) {
  994.                 nodeAnnos.push('url');
  995.             }
  996.  
  997.             forEach(nodeAnnos, function(attr) {
  998.                 var anno = BookmarkDatasource.MAP_NODE_TO_ANNO[attr];
  999.                 if (node.ntype == 'feed' && attr == 'url')
  1000.                     anno = "livemark/siteURI";  // Exception
  1001.                 if (placeAnnos.indexOf(anno) >= 0) {
  1002.                     placeAnnos.splice(placeAnnos.indexOf(anno), 1);
  1003.                     var value = Call(self.asvc, self.asvc.getItemAnnotation, 
  1004.                         itemId, anno);
  1005.                     if (value == node[attr]) {
  1006.                         return;
  1007.                     }
  1008.                 }
  1009.                 Call(self.asvc, self.asvc.setItemAnnotation, itemId, anno, 
  1010.                     node[attr], 0, self.asvc.EXPIRE_NEVER);
  1011.             });
  1012.  
  1013.             forEach(placeAnnos, function(anno) {
  1014.                 Call(self.asvc, self.asvc.removeItemAnnotation, itemId, anno);
  1015.             });
  1016.         }
  1017.  
  1018.         function SynchronizeTags() {
  1019.             // Sync tags
  1020.             var tags = uri ? 
  1021.                     Call(self.tsvc, self.tsvc.getTagsForURI, uri, {}).slice() :
  1022.                     [];
  1023.             var ntags = node.tags || [];
  1024.             if (!equals(tags, ntags)) {
  1025.                 // Delete extraneous tags
  1026.                 var extra = tags.filter(function(x) { 
  1027.                         return x && x.length && ntags.indexOf(x) < 0; } );
  1028.                 if (extra.length) {
  1029.                     Call(self.tsvc, self.tsvc.untagURI, uri, extra);
  1030.                 }
  1031.  
  1032.                 // Add missing tags
  1033.                 var missing = ntags.filter(function(x) {
  1034.                         return x.length && tags.indexOf(x) < 0; } );
  1035.                 if (missing.length) {
  1036.                     Call(self.tsvc, self.tsvc.tagURI, uri, missing);
  1037.                 }
  1038.             }
  1039.         }
  1040.  
  1041.         function SynchronizeChildren(isDeleted) {
  1042.             if (isDeleted) {
  1043.                 node = new Node(nid, { ntype: "folder" } );
  1044.             }
  1045.  
  1046.             if (!(node.ntype == 'folder' &&
  1047.                     place instanceof Ci.nsINavHistoryContainerResultNode))
  1048.                 return;
  1049.  
  1050.             // Deal with children.
  1051.             place.containerOpen = true;
  1052.  
  1053.             var children = [];
  1054.             for (var i = 0; i < place.childCount; ++i) {
  1055.                 var child = place.getChild(i);
  1056.                 children.push(self.MapNative(child.itemId));
  1057.             }
  1058.  
  1059.             if (!node.children) {
  1060.                 node.children = [];
  1061.             }
  1062.  
  1063.             // Filter "special" children.
  1064.             var nodeChildren = node.children.filter(function(x) {
  1065.                 return x != tnid && x != unid;
  1066.             } );
  1067.  
  1068.             // Do inserts and moves.
  1069.             forEach(nodeChildren, function(child) {
  1070.                 if (children.indexOf(child) >= 0) {
  1071.                     return;
  1072.                 }
  1073.                 if (Call(self.bmsvc, self.bmsvc.getItemIdForGUID, child) >= 0) {
  1074.                     Xmarks.LogWrite("Moving item " + ns.NodeName(child) + " to " + ns.NodeName(self.MapNative(itemId)));
  1075.                     Call(self.bmsvc, self.bmsvc.moveItem, self.MapNid(child),
  1076.                          itemId, -1);
  1077.                     return;
  1078.                 }
  1079.                 var cnode = ns.Node(child);
  1080.                 var newItemId = null;
  1081.                 Xmarks.LogWrite("Creating " + cnode.ntype + " " + ns.NodeName(child) + " ...");
  1082.                 switch (cnode.ntype) {
  1083.                 case 'bookmark': 
  1084.                 case 'microsummary':
  1085.                 case 'query':
  1086.                     newItemId = Call(self.bmsvc, self.bmsvc.insertBookmark, 
  1087.                         itemId, self.FixedURI(cnode.url), -1, cnode.name || "");
  1088.                     Xmarks.LogWrite("Bookmark created.");
  1089.                     break;
  1090.                 case 'folder':
  1091.                     newItemId = Call(self.bmsvc, self.bmsvc.createFolder, 
  1092.                         itemId, cnode.name || "", -1);
  1093.                     Xmarks.LogWrite("Folder created.");
  1094.                     break;
  1095.                 case 'separator':
  1096.                     newItemId = Call(self.bmsvc, self.bmsvc.insertSeparator, 
  1097.                         itemId, -1);
  1098.                     Xmarks.LogWrite("Separator created");
  1099.                     // XXX: Separator name?
  1100.                     break;
  1101.                 case 'feed':
  1102.                     newItemId = Call(self.lmsvc, self.lmsvc.createLivemark, 
  1103.                         itemId, cnode.name || "", self.FixedURI(cnode.url), 
  1104.                         self.FixedURI(cnode.feedurl), -1);
  1105.                     Xmarks.LogWrite("Feed created.");
  1106.                     break;
  1107.                 }
  1108.                 if (newItemId) {
  1109.                     Call(self.bmsvc, self.bmsvc.setItemGUID, newItemId, child);
  1110.                 }
  1111.             });
  1112.  
  1113.             // Do deletes.
  1114.             for (var i = 0; i < place.childCount; ++i) {
  1115.                 var childItemId = Call(place, place.getChild, i).itemId;
  1116.                 if (ns.Node(self.MapNative(childItemId), false, true)) {
  1117.                     continue;
  1118.                 }
  1119.                 try {
  1120.                     Xmarks.LogWrite("Removing " + childItemId + " : " + 
  1121.                             ns.NodeName(self.MapNative(childItemId)));
  1122.                     Call(self.bmsvc, self.bmsvc.removeChildAt, itemId, i--);
  1123.                 } catch(e) {
  1124.                     Xmarks.LogWrite("Warning: failed trying to remove " +
  1125.                         self.MapNative(childItemId));
  1126.                     Xmarks.LogWrite("Error was " + e.toSource());
  1127.                     i++;
  1128.                 }
  1129.             }
  1130.  
  1131.             // Do reorders.
  1132.             var extraIndex = nodeChildren.length;
  1133.             var reorders = [];
  1134.             for (var i = 0; i < place.childCount; ++i) {
  1135.                 var childItemId = place.getChild(i).itemId;
  1136.                 var index = nodeChildren.indexOf(self.MapNative(childItemId));
  1137.                 if (index < 0) {
  1138.                     index = extraIndex++;
  1139.                 }
  1140.                 if (Call(self.bmsvc, self.bmsvc.getItemIndex, childItemId) != 
  1141.                         index) {
  1142.                     reorders.push([childItemId, index]);
  1143.                 }
  1144.             }
  1145.  
  1146.             reorders.sort(function(a, b) { return a[1] - b[1]; });
  1147.  
  1148.             forEach(reorders, function(r) {
  1149.                 Xmarks.LogWrite("Setting index for " + 
  1150.                     ns.NodeName(self.MapNative(r[0])) + " to " + r[1]);
  1151.                 Call(self.bmsvc, self.bmsvc.setItemIndex, r[0], r[1]);
  1152.             } );
  1153.                 
  1154.             place.containerOpen = false;
  1155.         }
  1156.     },
  1157.  
  1158.     //
  1159.     // Functions for reading from the native store.
  1160.     //
  1161.  
  1162.     notify: function(timer) {
  1163.         var self = ot.self;
  1164.         self._func = self._OnTree;
  1165.         self._args = null;
  1166.         this.bmsvc.runInBatchMode(this, null);
  1167.     },
  1168.  
  1169.     _OnTree: function() {
  1170.         var self = ot.self;
  1171.         var items = ot.items;
  1172.         var result;
  1173.         var s = Date.now();
  1174.         while (items.length > 0 && Date.now() - s < 100) {
  1175.             var next = items.shift();
  1176.             var item = next[0];
  1177.             var pnid = next[2];
  1178.             var children = null;
  1179.  
  1180.             if (self.IsContainer(item)) {
  1181.                 children = self.PushChildren(item, items, ot.depthFirst);
  1182.             }
  1183.  
  1184.             try {
  1185.                 result = ot.action.apply(ot.Caller, [item, pnid, children]);
  1186.             } catch (e) {
  1187.                 Xmarks.LogWrite("OnTree error: " + e.toSource());
  1188.                 result = 3;
  1189.             }
  1190.  
  1191.             if (result)
  1192.                 break;
  1193.         }
  1194.  
  1195.         if (items.length > 0 && !result) {
  1196.             ot.timer.initWithCallback(self, 10,
  1197.                 Ci.nsITimer.TYPE_ONE_SHOT);
  1198.         } else {
  1199.             ot.complete.apply(ot.Caller, [result]);
  1200.         }
  1201.     },
  1202.  
  1203.     OnTree: function(Caller, action, complete) {
  1204.         ot = {}
  1205.         ot.self = this;
  1206.         ot.Caller = Caller;
  1207.         ot.action = action;
  1208.         ot.complete = complete;
  1209.         ot.depthfirst = false;
  1210.         ot.items = []
  1211.  
  1212.         this.PushRoot(this.bmsvc.bookmarksMenuFolder, ot.items);
  1213.         this.PushRoot(this.bmsvc.toolbarFolder, ot.items);
  1214.         this.PushRoot(this.bmsvc.unfiledBookmarksFolder, ot.items);
  1215.  
  1216.         ot.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1217.         ot.timer.initWithCallback(this, 10, Ci.nsITimer.TYPE_ONE_SHOT);
  1218.  
  1219.         return;
  1220.     },
  1221.  
  1222.     PushRoot: function(itemId, list) {
  1223.         var nid = null;
  1224.         try {
  1225.             nid = this.MapNative(itemId);
  1226.         } catch(e) {
  1227.             Xmarks.LogWrite("Error: PushRoot couldn't find root for " + itemId);
  1228.         }
  1229.  
  1230.         if (!nid) {
  1231.             return false;
  1232.         }
  1233.  
  1234.         var options = Call(this.hsvc, this.hsvc.getNewQueryOptions);
  1235.         var query = Call(this.hsvc, this.hsvc.getNewQuery);
  1236.         query.setFolders([itemId], 1);
  1237.         var result = Call(this.hsvc, this.hsvc.executeQuery, query, options);
  1238.         list.push([result.root, nid, null]);
  1239.         return true;
  1240.     },
  1241.  
  1242.     IsContainer: function(item) {
  1243.         return (BookmarkDatasource.MAP_PLACE_TYPE_TO_NTYPE[item.type] == 
  1244.                 'folder' && !this.lmsvc.isLivemark(item.itemId));
  1245.     },
  1246.  
  1247.     ProvideNodes: function(Caller, AddNode, Complete) {
  1248.         if (collectTimingInfo)
  1249.             StartTimes("ProvideNodes");
  1250.         this.pn = {}
  1251.         this.pn.Caller = Caller;
  1252.         this.pn.AddNode = AddNode;
  1253.         this.pn.Complete = Complete;
  1254.         if (!this.initialized) {
  1255.             Complete.apply(Caller, [6]);
  1256.             return;
  1257.         }
  1258.         this.OnTree(this, BookmarkDatasource.MapPlacesToNode, 
  1259.             BookmarkDatasource.ProvideNodesDone);
  1260.         return;
  1261.     },
  1262.  
  1263.     // Enumerate the children of item and push them
  1264.     // onto the provided list, according to depthFirst ordering.
  1265.     // Returns the list of child nids.
  1266.  
  1267.     PushChildren: function(item, list, depthFirst) {
  1268.         if (!(item instanceof Ci.nsINavHistoryContainerResultNode)) {
  1269.             throw Error("Expected a folder but got a non-container");
  1270.         }
  1271.  
  1272.         item.containerOpen = true;
  1273.         try {
  1274.             var pnid = this.MapNative(item.itemId);
  1275.         } catch (e) {
  1276.             Xmarks.LogWrite("PushChildren skipping bad parent");
  1277.             return [];
  1278.         }
  1279.         var cnids = []
  1280.  
  1281.         for (var i = 0; i < item.childCount; ++i) {
  1282.             child = item.getChild(i);
  1283.             try {
  1284.                 var cnid = this.MapNative(child.itemId)
  1285.             } catch(e) {
  1286.                 Xmarks.LogWrite("PushChildren skipping bad child");
  1287.                 continue;
  1288.             }
  1289.             cnids.push(cnid);
  1290.             if (depthFirst) {
  1291.                 list.splice(i, 0, [child, cnid, pnid]);
  1292.             } else {
  1293.                 list.push([child, cnid, pnid]);
  1294.             }
  1295.         }
  1296.         item.containerOpen = false;
  1297.         return cnids;
  1298.     },
  1299.  
  1300.     //
  1301.     // Observer functions.
  1302.     //
  1303.  
  1304.     WatchForChanges: function() {
  1305.         var watcher = new BookmarkWatcher();
  1306.         // start observing
  1307.         Call(this.bmsvc, this.bmsvc.addObserver, watcher, false);
  1308.  
  1309.         return watcher;
  1310.     }
  1311. };
  1312.  
  1313. function BookmarkWatcher(){
  1314.     this.lmsvc = Cc["@mozilla.org/browser/livemark-service;2"].
  1315.         getService(Ci.nsILivemarkService);
  1316.     this.mssvc = Cc["@mozilla.org/microsummary/service;1"].
  1317.         getService(Ci.nsIMicrosummaryService);
  1318. }
  1319.  
  1320. BookmarkWatcher.prototype = {
  1321.  
  1322.     lastModified: null,
  1323.  
  1324.     NotifyObservers: function(reason) {
  1325.         // Output Javascript milliseconds since 1970.
  1326.         var lm = Date.now();
  1327.         if (!this.lastModified || lm > this.lastModified) {
  1328.             this.lastModified = lm;
  1329.             var os = Cc["@mozilla.org/observer-service;1"]
  1330.                 .getService(Ci.nsIObserverService);
  1331.             os.notifyObservers(null, "foxmarks-datasourcechanged", 
  1332.                 lm + ";bookmarks");
  1333.         }
  1334.     },
  1335.  
  1336.     ////////////////////////////////////////////////////////////////////////////
  1337.     //
  1338.     // nsINavBookmarkObserver
  1339.  
  1340.     onItemAdded: function(itemId, folderId) { 
  1341.         if (!this.lmsvc.isLivemark(folderId)) {
  1342.             this.NotifyObservers("Added") 
  1343.         }
  1344.     },
  1345.  
  1346.     onItemRemoved: function(itemId, folderId) { 
  1347.         if (!this.lmsvc.isLivemark(folderId)) {
  1348.             this.NotifyObservers("Removed")
  1349.         }
  1350.     },
  1351.  
  1352.     onItemChanged: function(itemId, property) { 
  1353.         // Skip title change for Microsummaries
  1354.         if (this.mssvc.hasMicrosummary(itemId) && property == 'title')
  1355.             return;
  1356.  
  1357.         if (BookmarkDatasource.INTERESTING_PROPERTIES[property] ||
  1358.             BookmarkDatasource.MAP_ANNO_TO_NODE[property]) {
  1359.             this.NotifyObservers("Changed") 
  1360.         }
  1361.     },
  1362.  
  1363.     onItemMoved: function() { 
  1364.         this.NotifyObservers("Moved") 
  1365.     },
  1366.  
  1367.     onItemVisited: function() {},
  1368.     onBeginUpdateBatch: function() {},
  1369.     onEndUpdateBatch: function() {},
  1370.  
  1371.     QueryInterface: function(iid) {
  1372.         if (iid.equals(Ci.nsINavHistoryBatchCallback) ||
  1373.             iid.equals(Ci.nsINavBookmarkObserver))
  1374.             return this;
  1375.         throw Components.result.NS_ERROR_NO_INTERFACE;
  1376.     },
  1377. };
  1378.  
  1379.  
  1380.